
let date = new Date()
// 启动延迟5s （每次从失活 -> 启动 都会执行外层上下文代码）
// TODO: 主线程会等sw启动之后才会发出请求（当然如果是头次注册启动，那么是异步的不会阻塞主线程）
// while(true) {
//   if (new Date() - date > 5000) {
//     break
//   }
// }
// 定义缓存名称和版本
const CACHE_NAME = 'my-app-cache-v1';
const PRECACHE_URLS = [
  // '/',
  // '/service-worker/index.css',
  // '/service-worker/index.html',
  // '/styles/main.css',
  // '/script/main.js',
  // '/images/logo.png',
  // 添加其他需要预缓存的资源
];

const whiteList = [
  // '/service-worker/index.html',
  // '/service-worker/index.css'
]

// 安装阶段 - 预缓存关键资源（每次sw文件变化都会走install，然后activate会在合适的时候才执行（或者这里我们使用skipWaiting强制立即激活））
// 暂停启动不会走install
self.addEventListener('install', event => {
  console.log('sw 安装111')
  event.waitUntil( // 等待所有资源缓存完成（原理应该是内部等待promise状态变化）
    caches.open(CACHE_NAME)
      .then(cache => {
        // 缓存核心资源
        // 每次sw有更新，下载完新sw之后，都会立即走当前install hook，然后安装都会去请求需要缓存的静态资源
        return cache.addAll(PRECACHE_URLS) // 内部自动fetch列表中包含的所有资源吧并通过cache.put缓存request/response对
          .then(() => {
            // 向主线程发送消息
            self.clients.matchAll().then(clients => {
              clients.forEach(client => {
                client.postMessage({
                  type: 'LOG',
                  message: 'Service Worker 安装完成，资源已缓存'
                });
              });
            });
          });
      })
      .catch(err => {
        console.error('缓存失败:', err);
      })
  );
});

// 激活阶段 - 清理旧缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          // 删除旧版本的缓存
          if (cacheName !== CACHE_NAME) {
            return caches.delete(cacheName);
          }
        })
      );
    }).then(() => {
       // 向主线程（window上下文或者其它worker上下文）发送消息
       self.clients.matchAll().then(clients => {
        clients.forEach(client => {
          client.postMessage({
            type: 'LOG',
            message: 'Service Worker 激活'
          });
        });
      });
    })
  );
  
  // 立即接管所有客户端
  event.waitUntil(self.clients.claim());
});

// 拦截网络请求
self.addEventListener('fetch', event => {
  console.log('sw 拦截网络请求123')
  // 这里白名单过滤，只拦截转发对应请求，return就是走原请求
  // if (whiteList.findIndex(item => event.request.url.includes(item)) !== -1) {
  //   return;
  // }
  event.respondWith(
    // 首页请求默认也会被fetch捕获，但是为什么没有输出index.html？
    // 时序问题：Service Worker 的注册和激活有一个过程。当你第一次访问页面时，Service Worker 还在注册/安装/激活的过程中，这时 index.html 的请求可能已经发出并完成了，所以没有被 Service Worker 捕获到。
    // 导航请求的特殊处理：浏览器对导航请求（比如加载 index.html）的处理方式与其他资源请求（如 CSS、JS 等）不同。默认情况下，新的 Service Worker 不会控制页面，直到该页面重新加载或用户导航到其他页面。
    // 其实即使加载到新的sw.js，如果没有下面的强制skipWaiting，也不会立马更新
    caches.match(event.request)
      .then(response => {
        self.clients.matchAll().then(clients => {
          clients.forEach(client => {
            client.postMessage({
              type: 'LOG',
              message: 'sw fetch捕获 -- ' + event.request.url.split('/').pop()
            });
          });
        });
        // 如果在缓存中找到请求的资源，直接返回
        if (response) {
          self.clients.matchAll().then(clients => {
            clients.forEach(client => {
              client.postMessage({
                type: 'LOG',
                message: 'sw fetch 缓存复用 -- ' + event.request.url.split('/').pop()
              });
            });
          });
          return response;
        }
        
        // 对于未缓存的资源，从网络获取
        return fetch(event.request)
          .then(response => {
            // 检查响应是否有效（注意404/500/403等错误也会默认当普通响应缓存，这个点得注意）
            if (!response || response.status !== 200 || response.url.includes('extension://')) {
              return response;
            }

            // 克隆响应，因为响应是流，只能使用一次
            const responseToCache = response.clone();
            
            // 将新资源添加到缓存
            caches.open(CACHE_NAME)
              .then(cache => {
                cache.put(event.request, responseToCache);
              });
            
            return response;
          })
          .catch(() => {
            // 网络请求失败且资源不在缓存中
            // 可以返回自定义的离线页面
            return caches.match('/offline.html');
          });
      })
  );
});

console.log('sw.js 已加载111');
// self.skipWaiting(); // TODO: 新sw实例跳过等待，直接接管

// 处理后台同步
self.addEventListener('sync', event => {
  if (event.tag === 'sync-data') {
    event.waitUntil(
      // 这里执行后台同步任务
      syncData()
        .then(() => {
          // 通知所有客户端同步成功
          self.clients.matchAll().then(clients => {
            clients.forEach(client => {
              client.postMessage({
                type: 'SYNC_COMPLETE',
                message: '后台同步完成'
              });
            });
          });
        })
    );
  }
});

// 模拟后台同步任务
function syncData() {
  return new Promise((resolve) => {
    // 这里通常会有发送数据到服务器的逻辑
    setTimeout(() => {
      resolve();
    }, 2000);
  });
}

// 处理来自主线程的消息
self.addEventListener('message', event => {
  if (event.data.type === 'GET_CACHE') {
    caches.open(CACHE_NAME)
      .then(cache => {
        return cache.keys();
      })
      .then(keys => {
        event.ports[0].postMessage(keys.map(key => key.url));
      });
  }
});


/**
 * 现象记录
 * 1. 如果当前网页注册了sw，那么客户端会持续（间隔应该挺短）监听sw.js的变化，一旦有变化就加载下来，新的sw.js会被立马执行
 *    看到的结果就是代码内部外层上下文的代码立即执行了，比如当前文件中的console.log(xxx)立即执行了，但是旧sw.js服务还没有结束
 *    可以理解为旧的sw实例还是用的旧的代码，也就说明旧sw实例对应的那些生命周期钩子还是在继续执行旧的代码
 *    什么时候sw实例新旧替换更新sw服务？【所有用旧sw的页面都关闭后 ｜ 旧实例中执行self.skipWaiting() | 新sw.js外层上下文中执行self.skipWaiting()】
 */